Index: applications/humanres/widget/EmployeeScreens.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- applications/humanres/widget/EmployeeScreens.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ applications/humanres/widget/EmployeeScreens.xml (date 1585973547402) @@ -74,16 +74,21 @@ - - - - - + + + + +
+ + + + +
Index: themes/common-theme/template/macro/TextFormMacroLibrary.ftl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/template/macro/TextFormMacroLibrary.ftl (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ themes/common-theme/template/macro/TextFormMacroLibrary.ftl (date 1585973547609) @@ -58,7 +58,7 @@ <#macro renderFormClose> <#macro renderMultiFormClose> -<#macro renderFormatListWrapperOpen formName style columnStyles> +<#macro renderFormatListWrapperOpen formName style columnStyles id> <#macro renderFormatListWrapperClose formName> <#macro renderFormatHeaderOpen> Index: themes/common-theme/webapp/common/js/knockout/knockout-secure-binding.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/webapp/common/js/knockout/knockout-secure-binding.js (date 1585973547633) +++ themes/common-theme/webapp/common/js/knockout/knockout-secure-binding.js (date 1585973547633) @@ -0,0 +1,1068 @@ +/*! knockout-secure-binding - v0.5.5 - 2016-04-03 + * https://github.com/brianmhunt/knockout-secure-binding + * Copyright (c) 2013 - 2016 Brian M Hunt; License: MIT */ +;(function(factory) { + //AMD + if (typeof define === "function" && define.amd) { + define(["knockout", "exports"], factory); + //normal script tag + } else { + factory(ko); + } +}(function(ko, exports, undefined) { + var Identifier, Expression, Parser, Node; + + function value_of(item) { + if (item instanceof Identifier || item instanceof Expression) { + return item.get_value(); + } + return item; + } + + // We re-use the cache/parsing from the original binding provider, + // in nodeParamsToObject (ala. getComponentParamsFromCustomElement) + var originalBindingProviderInstance = new ko.bindingProvider(); + + // The following are also in ko.*, but not exposed. + function _object_map(source, mapping) { + // ko.utils.objectMap + if (!source) { + return source; + } + var target = {}; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = mapping(source[prop], prop, source); + } + } + return target; + } + + // ko.virtualElements.virtualNodeBindingValue + var commentNodesHaveTextProperty = document && document.createComment("test").text === ""; + var startCommentRegex = commentNodesHaveTextProperty ? /^$/ : /^\s*ko(?:\s+([\s\S]+))?\s*$/; + + function _virtualNodeBindingValue(node) { + var regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(startCommentRegex); + return regexMatch ? regexMatch[1] : null; + } + + + +Identifier = (function () { + function Identifier(parser, token, dereferences) { + this.token = token; + this.dereferences = dereferences; + this.parser = parser; + } + + /** + * Return the value of the given + * + * @param {Object} parent (optional) source of the identifier e.g. for + * membership. e.g. `a.b`, one would pass `a` in as + * the parent when calling lookup_value for `b`. + * @return {Mixed} The value of the token for this Identifier. + */ + Identifier.prototype.lookup_value = function (parent) { + var token = this.token, + parser = this.parser, + $context = parser.context, + $data = $context.$data || {}, + globals = parser.globals || {}; + + if (parent) { + return value_of(parent)[token]; + } + + // short circuits + switch (token) { + case '$element': return parser.node; + case '$context': return $context; + case '$data': return $data; + default: + } + + return $data[token] || $context[token] || globals[token]; + }; + + /** + * Apply all () and [] functions on the identifier to the lhs value e.g. + * a()[3] has deref functions that are essentially this: + * [_deref_call, _deref_this where this=3] + * + * @param {mixed} value Should be an object. + * @return {mixed} The dereferenced value. + */ + Identifier.prototype.dereference = function (value) { + var member, + refs = this.dereferences || [], + parser = this.parser, + $context = parser.context || {}, + $data = $context.$data || {}, + self = { // top-level `this` in function calls + $context: $context, + $data: $data, + globals: parser.globals || {}, + $element: parser.node + }, + last_value, // becomes `this` in function calls to object properties. + i, n; + + for (i = 0, n = refs.length; i < n; ++i) { + member = refs[i]; + if (member === true) { + value = value.call(last_value || self); + last_value = value; + } else { + last_value = value; + value = value[value_of(member)]; + } + } + return value; + }; + + /** + * Return the value as one would get it from the top-level i.e. + * $data.token/$context.token/globals.token; this does not return intermediate + * values on a chain of members i.e. $data.hello.there -- requesting the + * Identifier('there').value will return $data/$context/globals.there. + * + * This will dereference using () or [arg] member. + * @param {object | Identifier | Expression} parent + * @return {mixed} Return the primitive or an accessor. + */ + Identifier.prototype.get_value = function (parent) { + return this.dereference(this.lookup_value(parent)); + }; + + /** + * Set the value of the Identifier. + * + * @param {Mixed} new_value The value that Identifier is to be set to. + */ + Identifier.prototype.set_value = function (new_value) { + var parser = this.parser, + $context = parser.context, + $data = $context.$data || {}, + globals = parser.globals || {}, + refs = this.dereferences || [], + leaf = this.token, + i, n, root; + + if (Object.hasOwnProperty.call($data, leaf)) { + root = $data; + } else if (Object.hasOwnProperty.call($context, leaf)) { + root = $context; + } else if (Object.hasOwnProperty.call(globals, leaf)) { + root = globals; + } else { + throw new Error("Identifier::set_value -- " + + "The property '" + leaf + "' does not exist " + + "on the $data, $context, or globals."); + } + + // Degenerate case. {$data|$context|global}[leaf] = something; + n = refs.length; + if (n === 0) { + root[leaf] = new_value; + } + + // First dereference is {$data|$context|global}[token]. + root = root[leaf]; + + // We cannot use this.dereference because that gives the leaf; to evoke + // the ES5 setter we have to call `obj[leaf] = new_value` + for (i = 0; i < n - 1; ++i) { + leaf = refs[i]; + if (leaf === true) { + root = root(); + } else { + root = root[value_of(leaf)]; + } + } + + // We indicate that a dereference is a function when it is `true`. + if (refs[i] === true) { + throw new Error("Cannot assign a value to a function."); + } + + // Call the setter for the leaf. + root[value_of(refs[i])] = new_value; + }; + + return Identifier; +})(); + +/** + * Determine if a character is a valid item in an identifier. + * Note that we do not check whether the first item is a number, nor do we + * support unicode identifiers here. + * + * See: http://docstore.mik.ua/orelly/webprog/jscript/ch02_07.htm + * @param {String} ch The character + * @return {Boolean} True if [A-Za-z0-9_] + */ +function is_identifier_char(ch) { + return (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= 9) || + ch === '_' || ch === '$'; +} + + +Node = (function () { + function Node(lhs, op, rhs) { + this.lhs = lhs; + this.op = op; + this.rhs = rhs; + } + + var operators = { + // unary + '!': function not(a, b) { return !b; }, + '!!': function notnot(a, b) { return !!b; }, + // mul/div + '*': function mul(a, b) { return a * b; }, + '/': function div(a, b) { return a / b; }, + '%': function mod(a, b) { return a % b; }, + // sub/add + '+': function add(a, b) { return a + b; }, + '-': function sub(a, b) { return a - b; }, + // relational + '<': function lt(a, b) { return a < b; }, + '<=': function le(a, b) { return a <= b; }, + '>': function gt(a, b) { return a > b; }, + '>=': function ge(a, b) { return a >= b; }, + // TODO: 'in': function (a, b) { return a in b; }, + // TODO: 'instanceof': function (a, b) { return a instanceof b; }, + // equality + '==': function equal(a, b) { return a === b; }, + '!=': function ne(a, b) { return a !== b; }, + '===': function sequal(a, b) { return a === b; }, + '!==': function sne(a, b) { return a !== b; }, + // bitwise + '&': function bit_and(a, b) { return a & b; }, + '^': function xor(a, b) { return a ^ b; }, + '|': function bit_or(a, b) { return a | b; }, + // logic + '&&': function logic_and(a, b) { return a && b; }, + '||': function logic_or(a, b) { return a || b; }, + }; + + /* In order of precedence, see: + https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence#Table + */ + // logical not + operators['!'].precedence = 4; + operators['!!'].precedence = 4; // explicit double-negative + // multiply/divide/mod + operators['*'].precedence = 5; + operators['/'].precedence = 5; + operators['%'].precedence = 5; + // add/sub + operators['+'].precedence = 6; + operators['-'].precedence = 6; + // relational + operators['<'].precedence = 8; + operators['<='].precedence = 8; + operators['>'].precedence = 8; + operators['>='].precedence = 8; + // operators['in'].precedence = 8; + // operators['instanceof'].precedence = 8; + // equality + operators['=='].precedence = 9; + operators['!='].precedence = 9; + operators['==='].precedence = 9; + operators['!=='].precedence = 9; + // bitwise + operators['&'].precedence = 10; + operators['^'].precedence = 11; + operators['|'].precedence = 12; + // logic + operators['&&'].precedence = 13; + operators['||'].precedence = 14; + + Node.operators = operators; + + + Node.prototype.get_leaf_value = function (leaf, member_of) { + if (typeof(leaf) === 'function') { + // Expressions on observables are nonsensical, so we unwrap any + // function values (e.g. identifiers). + return ko.unwrap(leaf()); + } + + // primitives + if (typeof(leaf) !== 'object') { + return member_of ? member_of[leaf] : leaf; + } + + // Identifiers and Expressions + if (leaf instanceof Identifier || leaf instanceof Expression) { + // lhs is passed in as the parent of the leaf. It will be defined in + // cases like a.b.c as 'a' for 'b' then as 'b' for 'c'. + return ko.unwrap(leaf.get_value(member_of)); + } + + if (leaf instanceof Node) { + return leaf.get_node_value(member_of); + } + + throw new Error("Invalid type of leaf node: " + leaf); + }; + + /** + * Return a function that calculates and returns an expression's value + * when called. + * @param {array} ops The operations to perform + * @return {function} The function that calculates the expression. + * + * Exported for testing. + */ + Node.prototype.get_node_value = function () { + return this.op(this.get_leaf_value(this.lhs), + this.get_leaf_value(this.rhs)); + }; + + return Node; +})(); + +Expression = (function () { + function Expression(nodes) { + this.nodes = nodes; + this.root = this.build_tree(nodes); + } + + // Exports for testing. + Expression.operators = Node.operators; + Expression.Node = Node; + + /** + * Convert an array of nodes to an executable tree. + * @return {object} An object with a `lhs`, `rhs` and `op` key, corresponding + * to the left hand side, right hand side, and + * operation function. + */ + Expression.prototype.build_tree = function (nodes) { + var root, + leaf, + op, + value; + + // console.log("build_tree", nodes.slice(0)) + + // primer + leaf = root = new Node(nodes.shift(), nodes.shift(), nodes.shift()); + + while (nodes) { + op = nodes.shift(); + value = nodes.shift(); + if (!op) { + break; + } + if (op.precedence > root.op.precedence) { + // rebase + root = new Node(root, op, value); + leaf = root; + } else { + leaf.rhs = new Node(leaf.rhs, op, value); + leaf = leaf.rhs; + } + } + // console.log("tree", root) + return root; + }; // build_tree + + Expression.prototype.get_value = function () { + if (!this.root) { + this.root = this.build_tree(this.nodes); + } + return this.root.get_node_value(); + }; + + return Expression; +})(); + + +/** + * Originally based on (public domain): + * https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js + */ +/* jshint -W083 */ +Parser = (function () { + var escapee = { + "'": "'", + '"': '"', + '\\': '\\', + '/': '/', + b: '\b', + f: '\f', + n: '\n', + r: '\r', + t: '\t' + }, + operators = Expression.operators; + + /** + * Construct a new Parser instance with new Parser(node, context) + * @param {Node} node The DOM element from which we parsed the + * content. + * @param {object} context The Knockout context. + * @param {object} globals An object containing any desired globals. + */ + function Parser(node, context, globals) { + this.node = node; + this.context = context; + this.globals = globals || {}; + } + + // exported for testing. + Parser.Expression = Expression; + Parser.Identifier = Identifier; + Parser.Node = Node; + + Parser.prototype.white = function () { + var ch = this.ch; + while (ch && ch <= ' ') { + ch = this.next(); + } + return ch; + }; + + Parser.prototype.next = function (c) { + if (c && c !== this.ch) { + this.error("Expected '" + c + "' but got '" + this.ch + "'"); + } + this.ch = this.text.charAt(this.at); + this.at += 1; + return this.ch; + }; + + Parser.prototype.error = function (m) { + // console.trace() + throw { + name: 'SyntaxError', + message: m, + at: this.at, + text: this.text + }; + }; + + Parser.prototype.name = function () { + // A name of a binding + var name = ''; + this.white(); + + var ch = this.ch; + + while (ch) { + if (ch === ':' || ch <= ' ' || ch === ',') { + return name; + } + name += ch; + ch = this.next(); + } + + return name; + }; + + Parser.prototype.number = function () { + var number, + string = '', + ch = this.ch; + + if (ch === '-') { + string = '-'; + ch = this.next('-'); + } + while (ch >= '0' && ch <= '9') { + string += ch; + ch = this.next(); + } + if (ch === '.') { + string += '.'; + ch = this.next(); + while (ch && ch >= '0' && ch <= '9') { + string += ch; + ch = this.next(); + } + } + if (ch === 'e' || ch === 'E') { + string += ch; + ch = this.next(); + if (ch === '-' || ch === '+') { + string += ch; + ch = this.next(); + } + while (ch >= '0' && ch <= '9') { + string += ch; + ch = this.next(); + } + } + number = +string; + if (!isFinite(number)) { + error("Bad number"); + } else { + return number; + } + }; + + /** + * Add a property to 'object' that equals the given value. + * @param {Object} object The object to add the value to. + * @param {String} key object[key] is set to the given value. + * @param {mixed} value The value, may be a primitive or a function. If a + * function it is unwrapped as a property. + */ + Parser.prototype.object_add_value = function (object, key, value) { + if (value instanceof Identifier || value instanceof Expression) { + Object.defineProperty(object, key, { + get: function () { + return value.get_value(); + }, + enumerable: true, + }); + } else { + // primitives + object[key] = value; + } + }; + + Parser.prototype.object = function () { + var key, + object = {}, + ch = this.ch; + + if (ch === '{') { + this.next('{'); + ch = this.white(); + if (ch === '}') { + ch = this.next('}'); + return object; + } + while (ch) { + if (ch === '"' || ch === "'") { + key = this.string(); + } else { + key = this.name(); + } + this.white(); + ch = this.next(':'); + if (Object.hasOwnProperty.call(object, key)) { + this.error('Duplicate key "' + key + '"'); + } + + this.object_add_value(object, key, this.expression()); + + ch = this.white(); + if (ch === '}') { + ch = this.next('}'); + return object; + } + + this.next(','); + ch = this.white(); + } + } + this.error("Bad object"); + }; + + + /** + * Read up to delim and return the string + * @param {string} delim The delimiter, either ' or " + * @return {string} The string read. + */ + Parser.prototype.read_string = function (delim) { + var string = '', + hex, + i, + uffff, + ch = this.next(); + + while (ch) { + if (ch === delim) { + ch = this.next(); + return string; + } + if (ch === '\\') { + ch = this.next(); + if (ch === 'u') { + uffff = 0; + for (i = 0; i < 4; i += 1) { + hex = parseInt(ch = this.next(), 16); + if (!isFinite(hex)) { + break; + } + uffff = uffff * 16 + hex; + } + string += String.fromCharCode(uffff); + } else if (typeof escapee[ch] === 'string') { + string += escapee[ch]; + } else { + break; + } + } else { + string += ch; + } + ch = this.next(); + } + + this.error("Bad string"); + }; + + Parser.prototype.string = function () { + var ch = this.ch; + if (ch === '"') { + return this.read_string('"'); + } else if (ch === "'") { + return this.read_string("'"); + } + + this.error("Bad string"); + }; + + Parser.prototype.array = function () { + var array = [], + ch = this.ch; + if (ch === '[') { + ch = this.next('['); + this.white(); + if (ch === ']') { + ch = this.next(']'); + return array; + } + while (ch) { + array.push(this.expression()); + ch = this.white(); + if (ch === ']') { + ch = this.next(']'); + return array; + } + this.next(','); + ch = this.white(); + } + } + this.error("Bad array"); + }; + + Parser.prototype.value = function () { + var ch; + this.white(); + ch = this.ch; + switch (ch) { + case '{': return this.object(); + case '[': return this.array(); + case '"': case "'": return this.string(); + case '-': return this.number(); + default: + return ch >= '0' && ch <= '9' ? this.number() : this.identifier(); + } + }; + + /** + * Get the function for the given operator. + * A `.precedence` value is added to the function, with increasing + * precedence having a higher number. + * @return {function} The function that performs the infix operation + */ + Parser.prototype.operator = function () { + var op = '', + op_fn, + ch = this.white(); + + while (ch) { + if (is_identifier_char(ch) || ch <= ' ' || ch === '' || + ch === '"' || ch === "'" || ch === '{' || ch === '[' || + ch === '(') { + break; + } + op += ch; + ch = this.next(); + } + + if (op !== '') { + op_fn = operators[op]; + + if (!op_fn) { + this.error("Bad operator: '" + op + "'."); + } + } + + return op_fn; + }; + + /** + * Parse an expression – builds an operator tree, in something like + * Shunting-Yard. + * See: http://en.wikipedia.org/wiki/Shunting-yard_algorithm + * + * @return {function} A function that computes the value of the expression + * when called or a primitive. + */ + Parser.prototype.expression = function () { + var root, + nodes = [], + node_value, + ch = this.white(); + + while (ch) { + // unary prefix operators + op = this.operator(); + if (op) { + nodes.push(undefined); // padding. + nodes.push(op); + } + + if (ch === '(') { + this.next(); + nodes.push(this.expression()); + this.next(')'); + } else { + node_value = this.value(); + nodes.push(node_value); + } + ch = this.white(); + if (ch === ':' || ch === '}' || ch === ',' || ch === ']' || + ch === ')' || ch === '') { + break; + } + // infix operators + op = this.operator(); + if (op) { + nodes.push(op); + } + ch = this.white(); + } + + if (nodes.length === 0) { + return undefined; + } + + if (nodes.length === 1) { + return nodes[0]; + } + + return new Expression(nodes); + }; + + /** + * A dereference applies to an identifer, being either a function + * call "()" or a membership lookup with square brackets "[member]". + * @return {fn or undefined} Dereference function to be applied to the + * Identifier + */ + Parser.prototype.dereference = function () { + var member, + ch = this.white(); + + while (ch) { + if (ch === '(') { + // a() function call + this.next('('); + this.white(); + this.next(')'); + return true; // in Identifier::dereference we check this + } else if (ch === '[') { + // a[x] membership + this.next('['); + member = this.expression(); + this.white(); + this.next(']'); + + return member; + } else if (ch === '.') { + // a.x membership + member = ''; + this.next('.'); + ch = this.white(); + while (ch) { + if (!is_identifier_char(ch)) { + break; + } + member += ch; + ch = this.next(); + } + return member; + } else { + break; + } + ch = this.white(); + } + return; + }; + + Parser.prototype.identifier = function () { + var token = '', ch, deref, dereferences = []; + ch = this.white(); + while (ch) { + if (!is_identifier_char(ch)) { + break; + } + token += ch; + ch = this.next(); + } + switch (token) { + case 'true': return true; + case 'false': return false; + case 'null': return null; + // we use `void 0` because `undefined` can be redefined. + case 'undefined': return void 0; + default: + } + while (ch) { + deref = this.dereference(); + if (deref !== undefined) { + dereferences.push(deref); + } else { + break; + } + } + return new Identifier(this, token, dereferences); + }; + + Parser.prototype.bindings = function () { + var key, + bindings = {}, + sep, + ch = this.ch; + + while (ch) { + key = this.name(); + sep = this.white(); + if (!sep || sep === ',') { + if (sep) { + ch = this.next(','); + } else { + ch = ''; + } + // A "bare" binding e.g. "text"; substitute value of 'null' + // so it becomes "text: null". + bindings[key] = null; + } else { + ch = this.next(':'); + bindings[key] = this.expression(); + this.white(); + if (this.ch) { + ch = this.next(','); + } else { + ch = ''; + } + } + } + return bindings; + }; + +/** + * Convert result[name] from a value to a function (i.e. `valueAccessor()`) + * @param {object} result [Map of top-level names to values] + * @return {object} [Map of top-level names to functions] + * + * Accessors may be one of constAccessor (below), identifierAccessor or + * expressionAccessor. + */ + Parser.prototype.convert_to_accessors = function (result) { + var propertyWriters = {}; + ko.utils.objectForEach(result, function (name, value) { + if (value instanceof Identifier) { + // use _twoWayBindings so the binding can update Identifier + // See http://stackoverflow.com/questions/21580173 + result[name] = function () { + return value.get_value(); + }; + + if (ko.expressionRewriting._twoWayBindings[name]) { + propertyWriters[name] = function(new_value) { + value.set_value(new_value); + }; + } + } else if (value instanceof Expression) { + result[name] = function expressionAccessor() { + return value.get_value(); + }; + } else if (typeof(value) != 'function') { + result[name] = function constAccessor() { + return value; + }; + } + }); + + if (Object.keys(propertyWriters).length > 0) { + result._ko_property_writers = function () { + return propertyWriters; + }; + } + + return result; + }; + + /** + * Get the bindings as name: accessor() + * @param {string} source The binding string to parse. + * @return {object} Map of name to accessor function. + */ + Parser.prototype.parse = function (source) { + this.text = (source || '').trim(); + this.at = 0; + this.ch = ' '; + + if (!this.text) { + return null; + } + + var result = this.bindings(); + + this.white(); + if (this.ch) { + this.error("Syntax Error"); + } + + return this.convert_to_accessors(result); + }; + + return Parser; +})(); + + +// See knockout/src/binding/bindingProvider.js + +function secureBindingsProvider(options) { + var existingProvider = new ko.bindingProvider(); + options = options || {}; + + // override the attribute + this.attribute = options.attribute || "data-sbind"; + + // do we bind to the ko: virtual elements + this.noVirtualElements = options.noVirtualElements || false; + + // set globals + this.globals = options.globals || {}; + + // the binding classes -- defaults to ko bindingsHandlers + this.bindings = options.bindings || ko.bindingHandlers; + + // Cache the result of parsing binding strings. + // TODO + // this.cache = {}; +} + +function registerBindings(newBindings) { + ko.utils.extend(this.bindings, newBindings); +} + +function nodeHasBindings(node) { + var value; + if (node.nodeType === node.ELEMENT_NODE) { + return node.getAttribute(this.attribute) || + (ko.components && ko.components.getComponentNameForNode(node)); + } else if (node.nodeType === node.COMMENT_NODE) { + if (this.noVirtualElements) { + return false; + } + value = ("" + node.nodeValue || node.text).trim(); + // See also: knockout/src/virtualElements.js + return value.indexOf("ko ") === 0; + } +} + +function getBindingsString(node) { + switch (node.nodeType) { + case node.ELEMENT_NODE: + return node.getAttribute(this.attribute); + case node.COMMENT_NODE: + return _virtualNodeBindingValue(node); + default: + return null; + } +} + +// This mirrors ko's native getComponentParamsFromCustomElement +function nodeParamsToObject(node, parser) { + var accessors = parser.parse(node.getAttribute('params')); + if (!accessors || Object.keys(accessors).length === 0) { + return {$raw: {}}; + } + var rawParamComputedValues = _object_map(accessors, + function(paramValue, paramName) { + return ko.computed(paramValue, null, + { disposeWhenNodeIsRemoved: node } + ); + } + ); + var params = _object_map(rawParamComputedValues, + function(paramValueComputed, paramName) { + var paramValue = paramValueComputed.peek(); + if (!paramValueComputed.isActive()) { + return paramValue; + } else { + return ko.computed({ + read: function() { + return ko.unwrap(paramValueComputed()); + }, + write: ko.isWriteableObservable(paramValue) && function(value) { + paramValueComputed()(value); + }, + disposeWhenNodeIsRemoved: node + }); + } + } + ); + if (!params.hasOwnProperty('$raw')) { + params.$raw = rawParamComputedValues; + } + return params; +} + + +// Note we do not seem to need both getBindings and getBindingAccessors; just +// the latter appears to suffice. +// +// Return the name/valueAccessor pairs. +// (undocumented replacement for getBindings) +// see https://github.com/knockout/knockout/pull/742 +function getBindingAccessors(node, context) { + var bindings = {}, + component_name, + parser = new Parser(node, context, this.globals), + sbind_string = this.getBindingsString(node); + + if (node.nodeType === node.ELEMENT_NODE && ko.components) { + component_name = ko.components.getComponentNameForNode(node); + } + + if (sbind_string) { + bindings = parser.parse(sbind_string || ''); + } + + // emulate ko.components.addBindingsForCustomElement(bindings, node, + // context, true); + if (component_name) { + if (bindings.component) { + throw new Error("Cannot use a component binding on custom elements"); + } + var componentBindingValue = { + 'name': component_name, + 'params': nodeParamsToObject(node, parser), + }; + bindings.component = function() { return componentBindingValue; }; + } + + return bindings; +} + + +ko.utils.extend(secureBindingsProvider.prototype, { + registerBindings: registerBindings, + nodeHasBindings: nodeHasBindings, + getBindingAccessors: getBindingAccessors, + getBindingsString: getBindingsString, + nodeParamsToObject: nodeParamsToObject, + Parser: Parser +}); + if (!exports) { + ko.secureBindingsProvider = secureBindingsProvider; + } + return secureBindingsProvider; +})); \ No newline at end of file Index: framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java (date 1586443921734) @@ -88,6 +88,7 @@ import org.apache.ofbiz.widget.model.ModelTheme; import org.apache.ofbiz.widget.model.ModelWidget; import org.apache.ofbiz.widget.model.ThemeFactory; +import org.apache.ofbiz.widget.model.ViewModelUtil; import org.apache.ofbiz.widget.renderer.FormRenderer; import org.apache.ofbiz.widget.renderer.FormStringRenderer; import org.apache.ofbiz.widget.renderer.Paginator; @@ -219,6 +220,10 @@ title = description; description = description.substring(0, size - 8) + "..." + description.substring(description.length() - 5); } + String dataBind = modelFormField.getDataBind(); + if (ViewModelUtil.needDataBind(modelFormField.getModelForm()) && UtilValidate.isEmpty(dataBind)){ + dataBind = "text:"+modelFormField.getFieldName(); + } StringWriter sr = new StringWriter(); sr.append("<@renderDisplayField "); sr.append("type=\""); @@ -227,6 +232,8 @@ sr.append(imageLocation); sr.append("\" idName=\""); sr.append(idName); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" description=\""); sr.append(encodeDoubleQuotes(description)); sr.append("\" title=\""); @@ -391,10 +398,13 @@ boolean disabled = textField.getDisabled(); boolean readonly = textField.getReadonly(); String tabindex = modelFormField.getTabindex(); + String dataBind = modelFormField.getDataBind(); StringWriter sr = new StringWriter(); sr.append("<@renderTextField "); sr.append("name=\""); sr.append(name); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" className=\""); sr.append(className); sr.append("\" alert=\""); @@ -703,10 +713,13 @@ } } String tabindex = modelFormField.getTabindex(); + String dataBind = modelFormField.getDataBind(); StringWriter sr = new StringWriter(); sr.append("<@renderDateTimeField "); sr.append("name=\""); sr.append(name); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" className=\""); sr.append(className); sr.append("\" alert=\""); @@ -939,10 +952,13 @@ fullSearch = autoComplete.getFullSearch(); } String tabindex = modelFormField.getTabindex(); + String dataBind = modelFormField.getDataBind(); StringWriter sr = new StringWriter(); sr.append("<@renderDropDownField "); sr.append("name=\""); sr.append(name); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" className=\""); sr.append(className); sr.append("\" alert=\""); @@ -1424,6 +1440,7 @@ break; } } + String dataBind = modelForm.getDataBind(); String focusFieldName = modelForm.getFocusFieldName(); StringWriter sr = new StringWriter(); sr.append("<@renderFormOpen "); @@ -1441,6 +1458,8 @@ sr.append(autocomplete); sr.append("\" name=\""); sr.append(name); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" focusFieldName=\""); sr.append(focusFieldName); sr.append("\" hasRequiredField=\""); @@ -1562,7 +1581,10 @@ // this is a fix for forms with no fields sr.append(columnStyleListString); } - sr.append("] />"); + sr.append("] "); + sr.append(" id=\""); + sr.append(modelForm.getContainerId()); + sr.append("\" />"); executeMacro(writer, sr.toString()); } @@ -3288,6 +3310,7 @@ } targetParameters.append("}"); } + String dataBind = modelFormField.getDataBind(); StringWriter sr = new StringWriter(); sr.append("<@makeHyperlinkString "); sr.append("linkStyle=\""); @@ -3322,6 +3345,8 @@ sr.append(width); sr.append("\" id=\""); sr.append(id); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" />"); executeMacro(writer, sr.toString()); } @@ -3397,10 +3422,13 @@ public void renderContainerFindField(Appendable writer, Map context, ContainerField containerField) throws IOException { final String id = containerField.getModelFormField().getCurrentContainerId(context); String className = UtilFormatOut.checkNull(containerField.getModelFormField().getWidgetStyle()); + String dataBind = containerField.getModelFormField().getDataBind(); StringWriter sr = new StringWriter(); sr.append("<@renderContainerField "); sr.append("id=\""); sr.append(id); + sr.append("\" dataBind=\""); + sr.append(dataBind); sr.append("\" className=\""); sr.append(className); sr.append("\" />"); Index: themes/common-theme/webapp/common/js/big/big.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/webapp/common/js/big/big.js (date 1585973547621) +++ themes/common-theme/webapp/common/js/big/big.js (date 1585973547621) @@ -0,0 +1,941 @@ +/* + * big.js v5.2.2 + * A small, fast, easy-to-use library for arbitrary-precision decimal arithmetic. + * Copyright (c) 2018 Michael Mclaughlin + * https://github.com/MikeMcl/big.js/LICENCE + */ +;(function (GLOBAL) { + 'use strict'; + var Big, + + +/************************************** EDITABLE DEFAULTS *****************************************/ + + + // The default values below must be integers within the stated ranges. + + /* + * The maximum number of decimal places (DP) of the results of operations involving division: + * div and sqrt, and pow with negative exponents. + */ + DP = 20, // 0 to MAX_DP + + /* + * The rounding mode (RM) used when rounding to the above decimal places. + * + * 0 Towards zero (i.e. truncate, no rounding). (ROUND_DOWN) + * 1 To nearest neighbour. If equidistant, round up. (ROUND_HALF_UP) + * 2 To nearest neighbour. If equidistant, to even. (ROUND_HALF_EVEN) + * 3 Away from zero. (ROUND_UP) + */ + RM = 1, // 0, 1, 2 or 3 + + // The maximum value of DP and Big.DP. + MAX_DP = 1E6, // 0 to 1000000 + + // The maximum magnitude of the exponent argument to the pow method. + MAX_POWER = 1E6, // 1 to 1000000 + + /* + * The negative exponent (NE) at and beneath which toString returns exponential notation. + * (JavaScript numbers: -7) + * -1000000 is the minimum recommended exponent value of a Big. + */ + NE = -7, // 0 to -1000000 + + /* + * The positive exponent (PE) at and above which toString returns exponential notation. + * (JavaScript numbers: 21) + * 1000000 is the maximum recommended exponent value of a Big. + * (This limit is not enforced or checked.) + */ + PE = 21, // 0 to 1000000 + + +/**************************************************************************************************/ + + + // Error messages. + NAME = '[big.js] ', + INVALID = NAME + 'Invalid ', + INVALID_DP = INVALID + 'decimal places', + INVALID_RM = INVALID + 'rounding mode', + DIV_BY_ZERO = NAME + 'Division by zero', + + // The shared prototype object. + P = {}, + UNDEFINED = void 0, + NUMERIC = /^-?(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i; + + + /* + * Create and return a Big constructor. + * + */ + function _Big_() { + + /* + * The Big constructor and exported function. + * Create and return a new instance of a Big number object. + * + * n {number|string|Big} A numeric value. + */ + function Big(n) { + var x = this; + + // Enable constructor usage without new. + if (!(x instanceof Big)) return n === UNDEFINED ? _Big_() : new Big(n); + + // Duplicate. + if (n instanceof Big) { + x.s = n.s; + x.e = n.e; + x.c = n.c.slice(); + } else { + parse(x, n); + } + + /* + * Retain a reference to this Big constructor, and shadow Big.prototype.constructor which + * points to Object. + */ + x.constructor = Big; + } + + Big.prototype = P; + Big.DP = DP; + Big.RM = RM; + Big.NE = NE; + Big.PE = PE; + Big.version = '5.2.2'; + + return Big; + } + + + /* + * Parse the number or string value passed to a Big constructor. + * + * x {Big} A Big number instance. + * n {number|string} A numeric value. + */ + function parse(x, n) { + var e, i, nl; + + // Minus zero? + if (n === 0 && 1 / n < 0) n = '-0'; + else if (!NUMERIC.test(n += '')) throw Error(INVALID + 'number'); + + // Determine sign. + x.s = n.charAt(0) == '-' ? (n = n.slice(1), -1) : 1; + + // Decimal point? + if ((e = n.indexOf('.')) > -1) n = n.replace('.', ''); + + // Exponential form? + if ((i = n.search(/e/i)) > 0) { + + // Determine exponent. + if (e < 0) e = i; + e += +n.slice(i + 1); + n = n.substring(0, i); + } else if (e < 0) { + + // Integer. + e = n.length; + } + + nl = n.length; + + // Determine leading zeros. + for (i = 0; i < nl && n.charAt(i) == '0';) ++i; + + if (i == nl) { + + // Zero. + x.c = [x.e = 0]; + } else { + + // Determine trailing zeros. + for (; nl > 0 && n.charAt(--nl) == '0';); + x.e = e - i - 1; + x.c = []; + + // Convert string to array of digits without leading/trailing zeros. + for (e = 0; i <= nl;) x.c[e++] = +n.charAt(i++); + } + + return x; + } + + + /* + * Round Big x to a maximum of dp decimal places using rounding mode rm. + * Called by stringify, P.div, P.round and P.sqrt. + * + * x {Big} The Big to round. + * dp {number} Integer, 0 to MAX_DP inclusive. + * rm {number} 0, 1, 2 or 3 (DOWN, HALF_UP, HALF_EVEN, UP) + * [more] {boolean} Whether the result of division was truncated. + */ + function round(x, dp, rm, more) { + var xc = x.c, + i = x.e + dp + 1; + + if (i < xc.length) { + if (rm === 1) { + + // xc[i] is the digit after the digit that may be rounded up. + more = xc[i] >= 5; + } else if (rm === 2) { + more = xc[i] > 5 || xc[i] == 5 && + (more || i < 0 || xc[i + 1] !== UNDEFINED || xc[i - 1] & 1); + } else if (rm === 3) { + more = more || !!xc[0]; + } else { + more = false; + if (rm !== 0) throw Error(INVALID_RM); + } + + if (i < 1) { + xc.length = 1; + + if (more) { + + // 1, 0.1, 0.01, 0.001, 0.0001 etc. + x.e = -dp; + xc[0] = 1; + } else { + + // Zero. + xc[0] = x.e = 0; + } + } else { + + // Remove any digits after the required decimal places. + xc.length = i--; + + // Round up? + if (more) { + + // Rounding up may mean the previous digit has to be rounded up. + for (; ++xc[i] > 9;) { + xc[i] = 0; + if (!i--) { + ++x.e; + xc.unshift(1); + } + } + } + + // Remove trailing zeros. + for (i = xc.length; !xc[--i];) xc.pop(); + } + } else if (rm < 0 || rm > 3 || rm !== ~~rm) { + throw Error(INVALID_RM); + } + + return x; + } + + + /* + * Return a string representing the value of Big x in normal or exponential notation. + * Handles P.toExponential, P.toFixed, P.toJSON, P.toPrecision, P.toString and P.valueOf. + * + * x {Big} + * id? {number} Caller id. + * 1 toExponential + * 2 toFixed + * 3 toPrecision + * 4 valueOf + * n? {number|undefined} Caller's argument. + * k? {number|undefined} + */ + function stringify(x, id, n, k) { + var e, s, + Big = x.constructor, + z = !x.c[0]; + + if (n !== UNDEFINED) { + if (n !== ~~n || n < (id == 3) || n > MAX_DP) { + throw Error(id == 3 ? INVALID + 'precision' : INVALID_DP); + } + + x = new Big(x); + + // The index of the digit that may be rounded up. + n = k - x.e; + + // Round? + if (x.c.length > ++k) round(x, n, Big.RM); + + // toFixed: recalculate k as x.e may have changed if value rounded up. + if (id == 2) k = x.e + n + 1; + + // Append zeros? + for (; x.c.length < k;) x.c.push(0); + } + + e = x.e; + s = x.c.join(''); + n = s.length; + + // Exponential notation? + if (id != 2 && (id == 1 || id == 3 && k <= e || e <= Big.NE || e >= Big.PE)) { + s = s.charAt(0) + (n > 1 ? '.' + s.slice(1) : '') + (e < 0 ? 'e' : 'e+') + e; + + // Normal notation. + } else if (e < 0) { + for (; ++e;) s = '0' + s; + s = '0.' + s; + } else if (e > 0) { + if (++e > n) for (e -= n; e--;) s += '0'; + else if (e < n) s = s.slice(0, e) + '.' + s.slice(e); + } else if (n > 1) { + s = s.charAt(0) + '.' + s.slice(1); + } + + return x.s < 0 && (!z || id == 4) ? '-' + s : s; + } + + + // Prototype/instance methods + + + /* + * Return a new Big whose value is the absolute value of this Big. + */ + P.abs = function () { + var x = new this.constructor(this); + x.s = 1; + return x; + }; + + + /* + * Return 1 if the value of this Big is greater than the value of Big y, + * -1 if the value of this Big is less than the value of Big y, or + * 0 if they have the same value. + */ + P.cmp = function (y) { + var isneg, + x = this, + xc = x.c, + yc = (y = new x.constructor(y)).c, + i = x.s, + j = y.s, + k = x.e, + l = y.e; + + // Either zero? + if (!xc[0] || !yc[0]) return !xc[0] ? !yc[0] ? 0 : -j : i; + + // Signs differ? + if (i != j) return i; + + isneg = i < 0; + + // Compare exponents. + if (k != l) return k > l ^ isneg ? 1 : -1; + + j = (k = xc.length) < (l = yc.length) ? k : l; + + // Compare digit by digit. + for (i = -1; ++i < j;) { + if (xc[i] != yc[i]) return xc[i] > yc[i] ^ isneg ? 1 : -1; + } + + // Compare lengths. + return k == l ? 0 : k > l ^ isneg ? 1 : -1; + }; + + + /* + * Return a new Big whose value is the value of this Big divided by the value of Big y, rounded, + * if necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM. + */ + P.div = function (y) { + var x = this, + Big = x.constructor, + a = x.c, // dividend + b = (y = new Big(y)).c, // divisor + k = x.s == y.s ? 1 : -1, + dp = Big.DP; + + if (dp !== ~~dp || dp < 0 || dp > MAX_DP) throw Error(INVALID_DP); + + // Divisor is zero? + if (!b[0]) throw Error(DIV_BY_ZERO); + + // Dividend is 0? Return +-0. + if (!a[0]) return new Big(k * 0); + + var bl, bt, n, cmp, ri, + bz = b.slice(), + ai = bl = b.length, + al = a.length, + r = a.slice(0, bl), // remainder + rl = r.length, + q = y, // quotient + qc = q.c = [], + qi = 0, + d = dp + (q.e = x.e - y.e) + 1; // number of digits of the result + + q.s = k; + k = d < 0 ? 0 : d; + + // Create version of divisor with leading zero. + bz.unshift(0); + + // Add zeros to make remainder as long as divisor. + for (; rl++ < bl;) r.push(0); + + do { + + // n is how many times the divisor goes into current remainder. + for (n = 0; n < 10; n++) { + + // Compare divisor and remainder. + if (bl != (rl = r.length)) { + cmp = bl > rl ? 1 : -1; + } else { + for (ri = -1, cmp = 0; ++ri < bl;) { + if (b[ri] != r[ri]) { + cmp = b[ri] > r[ri] ? 1 : -1; + break; + } + } + } + + // If divisor < remainder, subtract divisor from remainder. + if (cmp < 0) { + + // Remainder can't be more than 1 digit longer than divisor. + // Equalise lengths using divisor with extra leading zero? + for (bt = rl == bl ? b : bz; rl;) { + if (r[--rl] < bt[rl]) { + ri = rl; + for (; ri && !r[--ri];) r[ri] = 9; + --r[ri]; + r[rl] += 10; + } + r[rl] -= bt[rl]; + } + + for (; !r[0];) r.shift(); + } else { + break; + } + } + + // Add the digit n to the result array. + qc[qi++] = cmp ? n : ++n; + + // Update the remainder. + if (r[0] && cmp) r[rl] = a[ai] || 0; + else r = [a[ai]]; + + } while ((ai++ < al || r[0] !== UNDEFINED) && k--); + + // Leading zero? Do not remove if result is simply zero (qi == 1). + if (!qc[0] && qi != 1) { + + // There can't be more than one zero. + qc.shift(); + q.e--; + } + + // Round? + if (qi > d) round(q, dp, Big.RM, r[0] !== UNDEFINED); + + return q; + }; + + + /* + * Return true if the value of this Big is equal to the value of Big y, otherwise return false. + */ + P.eq = function (y) { + return !this.cmp(y); + }; + + + /* + * Return true if the value of this Big is greater than the value of Big y, otherwise return + * false. + */ + P.gt = function (y) { + return this.cmp(y) > 0; + }; + + + /* + * Return true if the value of this Big is greater than or equal to the value of Big y, otherwise + * return false. + */ + P.gte = function (y) { + return this.cmp(y) > -1; + }; + + + /* + * Return true if the value of this Big is less than the value of Big y, otherwise return false. + */ + P.lt = function (y) { + return this.cmp(y) < 0; + }; + + + /* + * Return true if the value of this Big is less than or equal to the value of Big y, otherwise + * return false. + */ + P.lte = function (y) { + return this.cmp(y) < 1; + }; + + + /* + * Return a new Big whose value is the value of this Big minus the value of Big y. + */ + P.minus = P.sub = function (y) { + var i, j, t, xlty, + x = this, + Big = x.constructor, + a = x.s, + b = (y = new Big(y)).s; + + // Signs differ? + if (a != b) { + y.s = -b; + return x.plus(y); + } + + var xc = x.c.slice(), + xe = x.e, + yc = y.c, + ye = y.e; + + // Either zero? + if (!xc[0] || !yc[0]) { + + // y is non-zero? x is non-zero? Or both are zero. + return yc[0] ? (y.s = -b, y) : new Big(xc[0] ? x : 0); + } + + // Determine which is the bigger number. Prepend zeros to equalise exponents. + if (a = xe - ye) { + + if (xlty = a < 0) { + a = -a; + t = xc; + } else { + ye = xe; + t = yc; + } + + t.reverse(); + for (b = a; b--;) t.push(0); + t.reverse(); + } else { + + // Exponents equal. Check digit by digit. + j = ((xlty = xc.length < yc.length) ? xc : yc).length; + + for (a = b = 0; b < j; b++) { + if (xc[b] != yc[b]) { + xlty = xc[b] < yc[b]; + break; + } + } + } + + // x < y? Point xc to the array of the bigger number. + if (xlty) { + t = xc; + xc = yc; + yc = t; + y.s = -y.s; + } + + /* + * Append zeros to xc if shorter. No need to add zeros to yc if shorter as subtraction only + * needs to start at yc.length. + */ + if ((b = (j = yc.length) - (i = xc.length)) > 0) for (; b--;) xc[i++] = 0; + + // Subtract yc from xc. + for (b = i; j > a;) { + if (xc[--j] < yc[j]) { + for (i = j; i && !xc[--i];) xc[i] = 9; + --xc[i]; + xc[j] += 10; + } + + xc[j] -= yc[j]; + } + + // Remove trailing zeros. + for (; xc[--b] === 0;) xc.pop(); + + // Remove leading zeros and adjust exponent accordingly. + for (; xc[0] === 0;) { + xc.shift(); + --ye; + } + + if (!xc[0]) { + + // n - n = +0 + y.s = 1; + + // Result must be zero. + xc = [ye = 0]; + } + + y.c = xc; + y.e = ye; + + return y; + }; + + + /* + * Return a new Big whose value is the value of this Big modulo the value of Big y. + */ + P.mod = function (y) { + var ygtx, + x = this, + Big = x.constructor, + a = x.s, + b = (y = new Big(y)).s; + + if (!y.c[0]) throw Error(DIV_BY_ZERO); + + x.s = y.s = 1; + ygtx = y.cmp(x) == 1; + x.s = a; + y.s = b; + + if (ygtx) return new Big(x); + + a = Big.DP; + b = Big.RM; + Big.DP = Big.RM = 0; + x = x.div(y); + Big.DP = a; + Big.RM = b; + + return this.minus(x.times(y)); + }; + + + /* + * Return a new Big whose value is the value of this Big plus the value of Big y. + */ + P.plus = P.add = function (y) { + var t, + x = this, + Big = x.constructor, + a = x.s, + b = (y = new Big(y)).s; + + // Signs differ? + if (a != b) { + y.s = -b; + return x.minus(y); + } + + var xe = x.e, + xc = x.c, + ye = y.e, + yc = y.c; + + // Either zero? y is non-zero? x is non-zero? Or both are zero. + if (!xc[0] || !yc[0]) return yc[0] ? y : new Big(xc[0] ? x : a * 0); + + xc = xc.slice(); + + // Prepend zeros to equalise exponents. + // Note: reverse faster than unshifts. + if (a = xe - ye) { + if (a > 0) { + ye = xe; + t = yc; + } else { + a = -a; + t = xc; + } + + t.reverse(); + for (; a--;) t.push(0); + t.reverse(); + } + + // Point xc to the longer array. + if (xc.length - yc.length < 0) { + t = yc; + yc = xc; + xc = t; + } + + a = yc.length; + + // Only start adding at yc.length - 1 as the further digits of xc can be left as they are. + for (b = 0; a; xc[a] %= 10) b = (xc[--a] = xc[a] + yc[a] + b) / 10 | 0; + + // No need to check for zero, as +x + +y != 0 && -x + -y != 0 + + if (b) { + xc.unshift(b); + ++ye; + } + + // Remove trailing zeros. + for (a = xc.length; xc[--a] === 0;) xc.pop(); + + y.c = xc; + y.e = ye; + + return y; + }; + + + /* + * Return a Big whose value is the value of this Big raised to the power n. + * If n is negative, round to a maximum of Big.DP decimal places using rounding + * mode Big.RM. + * + * n {number} Integer, -MAX_POWER to MAX_POWER inclusive. + */ + P.pow = function (n) { + var x = this, + one = new x.constructor(1), + y = one, + isneg = n < 0; + + if (n !== ~~n || n < -MAX_POWER || n > MAX_POWER) throw Error(INVALID + 'exponent'); + if (isneg) n = -n; + + for (;;) { + if (n & 1) y = y.times(x); + n >>= 1; + if (!n) break; + x = x.times(x); + } + + return isneg ? one.div(y) : y; + }; + + + /* + * Return a new Big whose value is the value of this Big rounded using rounding mode rm + * to a maximum of dp decimal places, or, if dp is negative, to an integer which is a + * multiple of 10**-dp. + * If dp is not specified, round to 0 decimal places. + * If rm is not specified, use Big.RM. + * + * dp? {number} Integer, -MAX_DP to MAX_DP inclusive. + * rm? 0, 1, 2 or 3 (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_UP) + */ + P.round = function (dp, rm) { + var Big = this.constructor; + if (dp === UNDEFINED) dp = 0; + else if (dp !== ~~dp || dp < -MAX_DP || dp > MAX_DP) throw Error(INVALID_DP); + return round(new Big(this), dp, rm === UNDEFINED ? Big.RM : rm); + }; + + + /* + * Return a new Big whose value is the square root of the value of this Big, rounded, if + * necessary, to a maximum of Big.DP decimal places using rounding mode Big.RM. + */ + P.sqrt = function () { + var r, c, t, + x = this, + Big = x.constructor, + s = x.s, + e = x.e, + half = new Big(0.5); + + // Zero? + if (!x.c[0]) return new Big(x); + + // Negative? + if (s < 0) throw Error(NAME + 'No square root'); + + // Estimate. + s = Math.sqrt(x + ''); + + // Math.sqrt underflow/overflow? + // Re-estimate: pass x coefficient to Math.sqrt as integer, then adjust the result exponent. + if (s === 0 || s === 1 / 0) { + c = x.c.join(''); + if (!(c.length + e & 1)) c += '0'; + s = Math.sqrt(c); + e = ((e + 1) / 2 | 0) - (e < 0 || e & 1); + r = new Big((s == 1 / 0 ? '1e' : (s = s.toExponential()).slice(0, s.indexOf('e') + 1)) + e); + } else { + r = new Big(s); + } + + e = r.e + (Big.DP += 4); + + // Newton-Raphson iteration. + do { + t = r; + r = half.times(t.plus(x.div(t))); + } while (t.c.slice(0, e).join('') !== r.c.slice(0, e).join('')); + + return round(r, Big.DP -= 4, Big.RM); + }; + + + /* + * Return a new Big whose value is the value of this Big times the value of Big y. + */ + P.times = P.mul = function (y) { + var c, + x = this, + Big = x.constructor, + xc = x.c, + yc = (y = new Big(y)).c, + a = xc.length, + b = yc.length, + i = x.e, + j = y.e; + + // Determine sign of result. + y.s = x.s == y.s ? 1 : -1; + + // Return signed 0 if either 0. + if (!xc[0] || !yc[0]) return new Big(y.s * 0); + + // Initialise exponent of result as x.e + y.e. + y.e = i + j; + + // If array xc has fewer digits than yc, swap xc and yc, and lengths. + if (a < b) { + c = xc; + xc = yc; + yc = c; + j = a; + a = b; + b = j; + } + + // Initialise coefficient array of result with zeros. + for (c = new Array(j = a + b); j--;) c[j] = 0; + + // Multiply. + + // i is initially xc.length. + for (i = b; i--;) { + b = 0; + + // a is yc.length. + for (j = a + i; j > i;) { + + // Current sum of products at this digit position, plus carry. + b = c[j] + yc[i] * xc[j - i - 1] + b; + c[j--] = b % 10; + + // carry + b = b / 10 | 0; + } + + c[j] = b; + } + + // Increment result exponent if there is a final carry, otherwise remove leading zero. + if (b) ++y.e; + else c.shift(); + + // Remove trailing zeros. + for (i = c.length; !c[--i];) c.pop(); + y.c = c; + + return y; + }; + + + /* + * Return a string representing the value of this Big in exponential notation to dp fixed decimal + * places and rounded using Big.RM. + * + * dp? {number} Integer, 0 to MAX_DP inclusive. + */ + P.toExponential = function (dp) { + return stringify(this, 1, dp, dp); + }; + + + /* + * Return a string representing the value of this Big in normal notation to dp fixed decimal + * places and rounded using Big.RM. + * + * dp? {number} Integer, 0 to MAX_DP inclusive. + * + * (-0).toFixed(0) is '0', but (-0.1).toFixed(0) is '-0'. + * (-0).toFixed(1) is '0.0', but (-0.01).toFixed(1) is '-0.0'. + */ + P.toFixed = function (dp) { + return stringify(this, 2, dp, this.e + dp); + }; + + + /* + * Return a string representing the value of this Big rounded to sd significant digits using + * Big.RM. Use exponential notation if sd is less than the number of digits necessary to represent + * the integer part of the value in normal notation. + * + * sd {number} Integer, 1 to MAX_DP inclusive. + */ + P.toPrecision = function (sd) { + return stringify(this, 3, sd, sd - 1); + }; + + + /* + * Return a string representing the value of this Big. + * Return exponential notation if this Big has a positive exponent equal to or greater than + * Big.PE, or a negative exponent equal to or less than Big.NE. + * Omit the sign for negative zero. + */ + P.toString = function () { + return stringify(this); + }; + + + /* + * Return a string representing the value of this Big. + * Return exponential notation if this Big has a positive exponent equal to or greater than + * Big.PE, or a negative exponent equal to or less than Big.NE. + * Include the sign for negative zero. + */ + P.valueOf = P.toJSON = function () { + return stringify(this, 4); + }; + + + // Export + + + Big = _Big_(); + + Big['default'] = Big.Big = Big; + + //AMD. + if (typeof define === 'function' && define.amd) { + define(function () { return Big; }); + + // Node and other CommonJS-like environments that support module.exports. + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = Big; + + //Browser. + } else { + GLOBAL.Big = Big; + } +})(this); Index: framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ framework/webapp/src/main/resources/org/apache/ofbiz/webapp/freemarkerTransforms.properties (date 1586446297664) @@ -29,3 +29,5 @@ setRequestAttribute=org.apache.ofbiz.webapp.ftl.SetRequestAttributeMethod renderWrappedText=org.apache.ofbiz.webapp.ftl.RenderWrappedTextTransform setContextField=org.apache.ofbiz.webapp.ftl.SetContextFieldTransform +modelForForm=org.apache.ofbiz.webapp.ftl.ModelForFormTransform +modelForGrid=org.apache.ofbiz.webapp.ftl.ModelForGridTransform Index: framework/widget/src/test/java/org/apache/ofbiz/widget/model/ViewModelUtilTest.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/src/test/java/org/apache/ofbiz/widget/model/ViewModelUtilTest.java (date 1585973547578) +++ framework/widget/src/test/java/org/apache/ofbiz/widget/model/ViewModelUtilTest.java (date 1585973547578) @@ -0,0 +1,50 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.apache.ofbiz.widget.model; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Test; + +public class ViewModelUtilTest { + @Test + public void testGetModelMapForGridJs(){ + HttpServletRequest request = mock(HttpServletRequest.class); + + Map>> viewModelMap = new HashMap<>(); + List>modelGrid = new ArrayList<>(); + Map rowMap = new HashMap(); + rowMap.put("1", "one"); + rowMap.put("2", "two"); + modelGrid.add(rowMap); + viewModelMap.put("myform", modelGrid); + when(request.getAttribute(ViewModelUtil.requestAttributeViewModelGrid)).thenReturn(viewModelMap); + + assertEquals("[{\"1\":\"one\",\"2\":\"two\"}]",ViewModelUtil.getModelMapForGridJs(request, "myform")); + } +} Index: themes/common-theme/template/macro/FoFormMacroLibrary.ftl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/template/macro/FoFormMacroLibrary.ftl (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ themes/common-theme/template/macro/FoFormMacroLibrary.ftl (date 1585973547598) @@ -84,7 +84,7 @@ <#macro renderFormClose> <#macro renderMultiFormClose> -<#macro renderFormatListWrapperOpen formName style columnStyles>><#list columnStyles as columnStyle> <@getFoStyle columnStyle/>/> +<#macro renderFormatListWrapperOpen formName style columnStyles id>><#list columnStyles as columnStyle> <@getFoStyle columnStyle/>/> <#macro renderFormatListWrapperClose formName> <#macro renderFormatHeaderOpen> Index: themes/common-theme/webapp/common/js/knockout/knockout-config.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/webapp/common/js/knockout/knockout-config.js (date 1586435491736) +++ themes/common-theme/webapp/common/js/knockout/knockout-config.js (date 1586435491736) @@ -0,0 +1,87 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +var initVmFunc = function (parent, model, rowVmFunc) { + var extendMap = {}; + if (parent.extend) { + extendMap = parent.extend; + } + if (!Array.isArray(model)){ + for (var key in model) { + if (model.hasOwnProperty(key)) { + parent[key] = ko + .observable($("<#lt/> -<#macro renderDateTimeField name className alert dateType timeDropdownParamName defaultDateTimeString localizedIconTitle timeHourName timeMinutesName minutes isTwelveHour ampmName amSelected pmSelected compositeType timeDropdown="" classString="" hour1="" hour2="" shortDateInput="" title="" value="" size="" maxlength="" id="" formName="" mask="" event="" action="" step="" timeValues="" tabindex="" > +<#macro renderDateTimeField name dataBind className alert dateType timeDropdownParamName defaultDateTimeString localizedIconTitle timeHourName timeMinutesName minutes isTwelveHour ampmName amSelected pmSelected compositeType timeDropdown="" classString="" hour1="" hour2="" shortDateInput="" title="" value="" size="" maxlength="" id="" formName="" mask="" event="" action="" step="" timeValues="" tabindex="" > <#if dateType!="time" > tabindex="${tabindex}" name="${name}_i18n" <@renderClass className alert /><#rt/> <#if title?has_content> title="${title}" <#if value?has_content> value="${value}" + <#if dataBind?has_content> data-bind="${dataBind}" <#if size?has_content> size="${size}"<#rt/> <#if maxlength?has_content> maxlength="${maxlength}" <#if id?has_content> id="${id}_i18n"/><#rt/> @@ -103,6 +106,7 @@ tabindex="${tabindex}" name="${name}" <#if event?has_content && action?has_content> ${event}="${action}" <@renderClass className alert /><#rt/> <#if title?has_content> title="${title}" <#if value?has_content> value="${value}" + <#if dataBind?has_content> data-bind="${dataBind}" <#if size?has_content> size="${size}"<#rt/> <#if maxlength?has_content> maxlength="${maxlength}" <#if mask?has_content> data-mask="${mask}"<#rt/> @@ -140,7 +144,7 @@ -<#macro renderDropDownField name className alert id formName action explicitDescription options fieldName otherFieldName otherValue otherFieldSize ajaxEnabled ajaxOptions frequency minChars choices autoSelect partialSearch partialChars ignoreCase fullSearch conditionGroup="" tabindex="" multiple="" event="" size="" firstInList="" currentValue="" allowEmpty="" dDFCurrent="" noCurrentSelectedKey=""> +<#macro renderDropDownField name dataBind className alert id formName action explicitDescription options fieldName otherFieldName otherValue otherFieldSize ajaxEnabled ajaxOptions frequency minChars choices autoSelect partialSearch partialChars ignoreCase fullSearch conditionGroup="" tabindex="" multiple="" event="" size="" firstInList="" currentValue="" allowEmpty="" dDFCurrent="" noCurrentSelectedKey=""> <#if conditionGroup?has_content> @@ -150,7 +154,7 @@ data-other-field-name="${otherFieldName}" data-other-field-value='${otherValue?js_string}' data-other-field-size='${otherFieldSize}' - > + <#if dataBind?has_content> data-bind="${dataBind}"<#rt/>> <#if firstInList?has_content && currentValue?has_content && !multiple?has_content> <#rt/><#-- replace("\'","'") related to OFBIZ-6504 --> @@ -243,8 +247,8 @@ <#macro renderSingleFormFieldTitle> -<#macro renderFormOpen linkUrl formType name viewIndexField viewSizeField viewIndex viewSize targetWindow="" containerId="" containerStyle="" autocomplete="" useRowSubmit="" focusFieldName="" hasRequiredField=""> -
enctype="multipart/form-data"<#if targetWindow?has_content> target="${targetWindow}"<#if containerId?has_content> id="${containerId}" <#if focusFieldName?has_content> data-focus-field="${focusFieldName}" class="<#if containerStyle?has_content>${containerStyle}<#else>basic-form<#if hasRequiredField?has_content> requireValidation" onsubmit="javascript:submitFormDisableSubmits(this)"<#if autocomplete?has_content> autocomplete="${autocomplete}" name="${name}"><#lt/> +<#macro renderFormOpen linkUrl formType name viewIndexField viewSizeField viewIndex viewSize dataBind="" targetWindow="" containerId="" containerStyle="" autocomplete="" useRowSubmit="" focusFieldName="" hasRequiredField=""> + data-bind="${dataBind}"<#if formType=="upload"> enctype="multipart/form-data"<#if targetWindow?has_content> target="${targetWindow}"<#if containerId?has_content> id="${containerId}" <#if focusFieldName?has_content> data-focus-field="${focusFieldName}" class="<#if containerStyle?has_content>${containerStyle}<#else>basic-form<#if hasRequiredField?has_content> requireValidation" onsubmit="javascript:submitFormDisableSubmits(this)"<#if autocomplete?has_content> autocomplete="${autocomplete}" name="${name}"><#lt/> <#if useRowSubmit?has_content && useRowSubmit> <#if linkUrl?index_of("VIEW_INDEX") <= 0 && linkUrl?index_of(viewIndexField) <= 0> @@ -262,8 +266,8 @@
<#lt/> -<#macro renderFormatListWrapperOpen formName columnStyles style=""> - <#lt/> +<#macro renderFormatListWrapperOpen formName columnStyles style="" id=""> +
id="${id}" ><#lt/> <#macro renderFormatListWrapperClose formName> @@ -301,7 +305,7 @@ <#macro renderFormatItemRowOpen formName itemIndex="" altRowStyles="" evenRowStyle="" oddRowStyle=""> - <#if itemIndex%2==0><#if evenRowStyle?has_content>class="${evenRowStyle}<#if altRowStyles?has_content> ${altRowStyles}"<#elseif altRowStyles?has_content>class="${altRowStyles}"<#else><#if oddRowStyle?has_content>class="${oddRowStyle}<#if altRowStyles?has_content> ${altRowStyles}"<#elseif altRowStyles?has_content>class="${altRowStyles}" > + <#if itemIndex%2==0><#if evenRowStyle?has_content>class="${evenRowStyle}<#if altRowStyles?has_content> ${altRowStyles}"<#elseif altRowStyles?has_content>class="${altRowStyles}"<#else><#if oddRowStyle?has_content>class="${oddRowStyle}<#if altRowStyles?has_content> ${altRowStyles}"<#elseif altRowStyles?has_content>class="${altRowStyles}" > <#macro renderFormatItemRowClose formName> @@ -633,7 +637,9 @@
-<#macro renderContainerField id className>
+<#macro renderContainerField id dataBind className> +
<#rt/> + <#macro renderFieldGroupOpen collapsed collapsibleAreaId collapsible expandToolTip collapseToolTip style="" id="" title=""> <#if style?has_content || id?has_content || title?has_content>
id="${id}"> @@ -691,7 +697,7 @@ <#if confirmation?has_content> onclick="return confirm('${confirmation?js_string}')"> <#if imgSrc?has_content>${description} -<#macro makeHyperlinkString hiddenFormName imgSrc title alternate linkUrl description linkStyle="" event="" action="" targetParameters="" targetWindow="" confirmation="" uniqueItemName="" height="" width="" id=""> +<#macro makeHyperlinkString dataBind hiddenFormName imgSrc title alternate linkUrl description linkStyle="" event="" action="" targetParameters="" targetWindow="" confirmation="" uniqueItemName="" height="" width="" id=""> <#if uniqueItemName?has_content> <#local params = "{"presentation": "layer""> <#if targetParameters?has_content> @@ -708,6 +714,7 @@ data-dialog-height="${height}" data-dialog-url="${linkUrl}" <#if text?has_content>data-dialog-title="${text}" + <#if dataBind?has_content> data-bind="${dataBind}" <#if linkStyle?has_content>class="${linkStyle}"> <#if description?has_content>${description} <#else> @@ -716,6 +723,7 @@ <#if action?has_content && event?has_content> ${event}="${action}" <#if confirmation?has_content> data-confirm-message="${confirmation}" <#if id?has_content> id="${id}" + <#if dataBind?has_content> data-bind="${dataBind}" <#if imgSrc?length == 0 && title?has_content> title="${title}"> <#if imgSrc?has_content>${alternate}${description} Index: applications/humanres/widget/forms/EmployeeForms.AddEmployee.js.ftl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- applications/humanres/widget/forms/EmployeeForms.AddEmployee.js.ftl (date 1586446407013) +++ applications/humanres/widget/forms/EmployeeForms.AddEmployee.js.ftl (date 1586446407013) @@ -0,0 +1,22 @@ + \ No newline at end of file Index: framework/widget/src/main/java/org/apache/ofbiz/widget/model/ViewModelUtil.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/src/main/java/org/apache/ofbiz/widget/model/ViewModelUtil.java (date 1586445913498) +++ framework/widget/src/main/java/org/apache/ofbiz/widget/model/ViewModelUtil.java (date 1586445913498) @@ -0,0 +1,156 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.apache.ofbiz.widget.model; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.ofbiz.base.lang.JSON; +import org.apache.ofbiz.base.util.UtilGenerics; +import org.apache.ofbiz.base.util.UtilValidate; + +public class ViewModelUtil { + + public static final String requestAttributeViewModelForm = "ViewModelForm"; + public static final String requestAttributeViewModelGrid = "ViewModelGrid"; + + public static String getModelMapForFormJs(HttpServletRequest request, String formName) { + Map> modelViewMap = UtilGenerics.cast(request.getAttribute(requestAttributeViewModelForm)); + if (modelViewMap!=null){ + try { + return JSON.from(modelViewMap.get(formName)).toString(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return "{}"; + } + + public static String getModelMapForGridJs(HttpServletRequest request, String formName) { + Map>> viewModelMap = UtilGenerics.cast(request.getAttribute(requestAttributeViewModelGrid)); + if (viewModelMap!=null){ + try { + return JSON.from(viewModelMap.get(formName)).toString(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return "[]"; + } + + public static void addFieldListToModelMapForm(Map context, List formFieldList){ + for(ModelFormField field : formFieldList){ + addFieldToModelMapForm(context, field, false); + } + } + + public static void addFieldToModelMapForm(Map context, ModelFormField currentFormField, boolean includeValue){ + String value = ""; + if (includeValue) { + getFieldValue(context,currentFormField); + } + String formName = currentFormField.getModelForm().getName(); + HttpServletRequest request = (HttpServletRequest)context.get("request"); + addToModelFieldForm(request, formName, currentFormField.getFieldName(), value); + } + + private static void addToModelFieldForm(HttpServletRequest request, String formName, String fieldName, String value){ + Map> modelViewMap = UtilGenerics.cast(request.getAttribute(ViewModelUtil.requestAttributeViewModelForm)); + if (modelViewMap==null){ + modelViewMap = new HashMap>(); + request.setAttribute(ViewModelUtil.requestAttributeViewModelForm, modelViewMap); + } + Map formMap = modelViewMap.get(formName); + if (formMap==null){ + formMap = new HashMap(); + modelViewMap.put(formName, formMap); + } + formMap.put(fieldName,value); + } + + public static boolean needDataBind(ModelForm currentForm){ + if (UtilValidate.isEmpty(currentForm.getDataBind())){ + return false; + } + return true; + } + + public static void addFieldToModelMapGrid(Map context, ModelFormField currentFormField, int rowIndex){ + String value = getFieldValue(context,currentFormField); + String formName = currentFormField.getModelForm().getName(); + HttpServletRequest request = (HttpServletRequest)context.get("request"); + addToModelMapGrid(request, formName, currentFormField.getFieldName(), value, rowIndex); + } + + private static String getFieldValue(Map context, ModelFormField currentFormField){ + int fieldType = currentFormField.getFieldInfo().getFieldType(); + String value = ""; + switch (fieldType){ + case FieldInfo.TEXT: { + ModelFormField.TextField field = (ModelFormField.TextField)currentFormField.getFieldInfo(); + value = currentFormField.getEntry(context, field.getDefaultValue(context)); + break; + } + case FieldInfo.DATE_TIME: { + ModelFormField.DateTimeField field = (ModelFormField.DateTimeField)currentFormField.getFieldInfo(); + value = currentFormField.getEntry(context, field.getDefaultValue(context)); + } + break; + default:{ + // FieldInfo.DISPLAY, FieldInfo.HYPERLINK, FieldInfo.DROP_DOWN, FieldInfo.DISPLAY_ENTITY + value = currentFormField.getEntry(context); + } + break; + } + return value; + } + + private static void addToModelMapGrid(HttpServletRequest request, String formName, String fieldName, String value, int index){ + Map>> modelViewMap = UtilGenerics.cast(request.getAttribute(requestAttributeViewModelGrid)); + if (modelViewMap==null){ + modelViewMap = new HashMap>>(); + request.setAttribute(requestAttributeViewModelGrid, modelViewMap); + } + List> gridMap = modelViewMap.get(formName); + if (gridMap==null){ + gridMap = new ArrayList<>(); + modelViewMap.put(formName, gridMap); + } + if (gridMap.size()()); + } + gridMap.get(index).put(fieldName,value); + } + + public static void addHiddenIgnoredFieldsToModelMap(Map context, + List fieldList, boolean forGrid, int itemIndex) { + for (ModelFormField modelFormField : fieldList) { + if (forGrid) { + ViewModelUtil.addFieldToModelMapGrid(context, modelFormField, itemIndex); + } else { + ViewModelUtil.addFieldToModelMapForm(context, modelFormField, true); + } + } + } +} Index: themes/common-theme/template/macro/CsvFormMacroLibrary.ftl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/template/macro/CsvFormMacroLibrary.ftl (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ themes/common-theme/template/macro/CsvFormMacroLibrary.ftl (date 1585973547582) @@ -58,7 +58,7 @@ <#macro renderFormClose> <#macro renderMultiFormClose> -<#macro renderFormatListWrapperOpen formName style columnStyles> +<#macro renderFormatListWrapperOpen formName style columnStyles id> <#macro renderFormatListWrapperClose formName> <#macro renderFormatHeaderOpen> Index: framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ModelForFormTransform.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ModelForFormTransform.java (date 1586446221741) +++ framework/webapp/src/main/java/org/apache/ofbiz/webapp/ftl/ModelForFormTransform.java (date 1586446221741) @@ -0,0 +1,70 @@ +/******************************************************************************* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + *******************************************************************************/ +package org.apache.ofbiz.webapp.ftl; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.ofbiz.widget.model.ViewModelUtil; + +import freemarker.core.Environment; +import freemarker.ext.beans.BeanModel; +import freemarker.template.TemplateTransformModel; + +public class ModelForFormTransform implements TemplateTransformModel { + public final static String module = ModelForFormTransform.class.getName(); + + @Override + public Writer getWriter(Writer out, @SuppressWarnings("rawtypes") Map args) { + + final StringBuffer buf = new StringBuffer(); + + return new Writer(out) { + + @Override + public void close() throws IOException { + try { + Environment env = Environment.getCurrentEnvironment(); + BeanModel req = (BeanModel) env.getVariable("request"); + if (req != null) { + HttpServletRequest request = (HttpServletRequest) req.getWrappedObject(); + String modelMap = ViewModelUtil.getModelMapForFormJs(request, buf.toString()); + out.write(modelMap); + } + return; + } catch (Exception e) { + throw new IOException(e.getMessage()); + } + } + + @Override + public void write(char cbuf[], int off, int len) { + buf.append(cbuf, off, len); + } + + @Override + public void flush() throws IOException { + out.flush(); + } + }; + } +} Index: themes/common-theme/template/macro/XmlFormMacroLibrary.ftl IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/template/macro/XmlFormMacroLibrary.ftl (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ themes/common-theme/template/macro/XmlFormMacroLibrary.ftl (date 1585973547616) @@ -19,7 +19,7 @@ <#macro renderField text=""><#if text??>${text?xml} -<#macro renderFormatListWrapperOpen formName style columnStyles><${formName}Export> +<#macro renderFormatListWrapperOpen formName style columnStyles id><${formName}Export> <#macro renderFormatListWrapperClose formName> Index: applications/order/widget/ordermgr/QuoteForms.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- applications/order/widget/ordermgr/QuoteForms.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ applications/order/widget/ordermgr/QuoteForms.xml (date 1585973547426) @@ -241,6 +241,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Index: themes/common-theme/widget/Theme.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- themes/common-theme/widget/Theme.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ themes/common-theme/widget/Theme.xml (date 1585973547640) @@ -80,6 +80,10 @@ + + + + Index: framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelForm.java (date 1585973547462) @@ -192,6 +192,7 @@ /** Keeps track of conditional fields to help ensure that only one is rendered */ private final Set useWhenFields; + private final String dataBind; /** XML Constructor */ protected ModelForm(Element formElement, String formLocation, ModelReader entityModelReader, @@ -742,6 +743,7 @@ focusFieldName = parentModel.focusFieldName; } this.focusFieldName = focusFieldName; + this.dataBind = formElement.getAttribute("data-bind"); } private void addAutoFieldsFromEntity(AutoFieldsEntity autoFieldsEntity, ModelReader entityModelReader, @@ -913,6 +915,10 @@ return this.containerStyle; } + public String getDataBind() { + return this.dataBind; + } + public String getDefaultEntityName() { return this.defaultEntityName; } Index: applications/datamodel/entitydef/order-entitymodel.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- applications/datamodel/entitydef/order-entitymodel.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ applications/datamodel/entitydef/order-entitymodel.xml (date 1585973547371) @@ -1535,6 +1535,17 @@ + + + + + + + + + UTF-8 =================================================================== --- applications/order/widget/ordermgr/OrderMenus.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ applications/order/widget/ordermgr/OrderMenus.xml (date 1585973547418) @@ -165,6 +165,15 @@ + + + + + + + + + Index: applications/humanres/widget/forms/EmployeeForms.xml IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- applications/humanres/widget/forms/EmployeeForms.xml (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ applications/humanres/widget/forms/EmployeeForms.xml (date 1585973547398) @@ -21,11 +21,12 @@ + header-row-style="header-row" default-table-style="basic-table" data-bind="submit:doSomething"> - - - + + + + @@ -34,13 +35,13 @@ - + - + Index: framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormFieldBuilder.java (date 1585973547496) @@ -114,6 +114,7 @@ private String parentFormName = ""; private String tabindex = ""; private String conditionGroup = ""; + private String dataBind = ""; protected static final List numericFieldTypes = Collections.unmodifiableList(UtilMisc.toList( "floating-point", "numeric", "fixed-point", @@ -203,6 +204,7 @@ this.parentFormName = fieldElement.getAttribute("form-name"); this.tabindex = fieldElement.getAttribute("tabindex"); this.conditionGroup = fieldElement.getAttribute("condition-group"); + this.dataBind = fieldElement.getAttribute("data-bind"); Element childElement = null; List subElements = UtilXml.childElementList(fieldElement); for (Element subElement : subElements) { @@ -319,6 +321,8 @@ this.parentFormName = modelFormField.getParentFormName(); this.tabindex = modelFormField.getTabindex(); this.conditionGroup = modelFormField.getConditionGroup(); + // needed by datetime field, but not text & dropdown field + this.dataBind = modelFormField.getDataBind(); } public ModelFormFieldBuilder(ModelFormFieldBuilder builder) { @@ -361,6 +365,7 @@ this.parentFormName = builder.getParentFormName(); this.tabindex = builder.getTabindex(); this.conditionGroup = builder.getConditionGroup(); + //this.dataBind = builder.getDataBind(); } public ModelFormFieldBuilder addOnChangeUpdateArea(UpdateArea onChangeUpdateArea) { @@ -540,6 +545,9 @@ public String getConditionGroup() { return conditionGroup; } + public String getDataBind() { + return dataBind; + } private boolean induceFieldInfo(ModelForm modelForm, String defaultFieldType, ModelReader entityModelReader, DispatchContext dispatchContext) { if (induceFieldInfoFromEntityField(defaultFieldType, entityModelReader)) { @@ -821,6 +829,9 @@ if (UtilValidate.isNotEmpty(builder.getConditionGroup())) { this.conditionGroup = builder.getConditionGroup(); } + if (UtilValidate.isNotEmpty(builder.getDataBind())) { + this.dataBind = builder.getDataBind(); + } this.encodeOutput = builder.getEncodeOutput(); this.position = builder.getPosition(); this.requiredField = builder.getRequiredField(); Index: framework/widget/dtd/widget-form.xsd IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== --- framework/widget/dtd/widget-form.xsd (revision 8ee522e42e9a3498350f52b3c8c812dcce14e7a2) +++ framework/widget/dtd/widget-form.xsd (date 1585973547453) @@ -241,6 +241,11 @@ Tells the browser whether or not to try and autocomplete with values previously entered. Default to true. + + + For binding behaviors to Form + +